#pragma once

#ifndef LHJ_CORE_LOGGER_LOGGER_H__
#define LHJ_CORE_LOGGER_LOGGER_H__

#include <string>
#include <fstream>
#include <atomic>
#include <queue>
#include <thread>
#include <cassert>
#include <memory>
#include <condition_variable>
#include <stdint.h>
#include <vector>
#include <mutex>

#define XLOG(format, ...) cores::loggers::Logger::Log(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)
#define XINFO(format, ...) cores::loggers::Logger::Info(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)
#define XDEBUG(format, ...) cores::loggers::Logger::Debug(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)
#define XWARN(format, ...) cores::loggers::Logger::Warn(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)
#define XERROR(format, ...) cores::loggers::Logger::Error(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)

namespace cores
{
    namespace loggers
    {
        class Logger
        {
        public:
            class XLock
            {
            public:
                XLock() : _flag(false) {}

                inline void lock()
                {
                    auto expect = false;
                    while (!_flag.compare_exchange_weak(expect, true))
                    {
                        expect = false;
                    }
                }

                inline void unlock()
                {
                    _flag.store(false);
                }

            protected:
                std::atomic<bool> _flag;
            };

            enum Level
            {
                ELog = 0,
                EInfo,
                EDebug,
                EWarn,
                EError,
                EAll,
                EMax
            };

            struct Data
            {
                int level_;
                int line_;
                uint64_t time_;
                int size_;
                std::vector<char> buffer_;
                std::string file_;
                std::string func_;
                std::thread::id threadId_;
            };

            Logger();
            virtual ~Logger();

            static void Log(const char *file, int line, const char *func, const char *format, ...);
            static void Info(const char *file, int line, const char *func, const char *format, ...);
            static void Debug(const char *file, int line, const char *func, const char *format, ...);
            static void Warn(const char *file, int line, const char *func, const char *format, ...);
            static void Error(const char *file, int line, const char *func, const char *format, ...);

            void setLogDir(const std::string &logDir);
            inline void setConsoleLog(Level level);

            static Logger *instance();

            void stop();

        protected:
            void print(const char *file, int line, const char *func, int level, Data *data);
            static void onRun(Logger *logger);
            virtual void run();

        protected:
            int consoleLog_;
            volatile bool isRunning_;
            Level level_;
            std::string logDir_;
            std::string logPaths_[EMax];
            std::ofstream files_[EMax];

            XLock lock_;
            std::mutex mutex_;
            std::condition_variable cv_;
            std::queue<Data *> queue_;

            static Logger *globalInstance;
        };
    }
}

#endif